﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toenda.Foundation.Checksum;

namespace Toenda.Foundation.Types {
	/// <summary>
	/// Implements a Securities Identification Number from the CUSIP group
	/// </summary>
	public struct CUSIP {
		private string _emitent;
		private string _type;
		private int? _checkNumber;

		/// <summary>
		/// Creates a new object of <see cref="CUSIP"/>
		/// </summary>
		/// <param name="value">A string that contains a CUSIP.</param>
		public CUSIP(string value) {
			CUSIP obj = CUSIP.Parse(value);

			this._emitent = obj.Emitent;
			this._type = obj.Type;
			this._checkNumber = obj.CheckNumber;
		}

		/// <summary>
		/// Creates a new object of <see cref="CUSIP"/>
		/// </summary>
		/// <param name="emitent">The emitent part of a CUSIP.</param>
		/// <param name="type">The type part of a CUSIP.</param>
		public CUSIP(string emitent, string type) {
			this._emitent = emitent;
			this._type = type;
			this._checkNumber = ComputeChecksum(emitent + type);
		}

		/// <summary>
		/// Creates a new object of <see cref="CUSIP"/>
		/// </summary>
		/// <param name="emitent">The emitent part of a CUSIP.</param>
		/// <param name="type">The type part of a CUSIP.</param>
		/// <param name="checkNumber">The checknumber of the CUSIP.</param>
		public CUSIP(string emitent, string type, int checkNumber) {
			this._emitent = emitent;
			this._type = type;
			this._checkNumber = checkNumber;
		}

		/// <summary>
		/// Get or set the emitent part
		/// </summary>
		public string Emitent {
			get { return this._emitent; }
			set { this._emitent = value; }
		}

		/// <summary>
		/// Get or set the _type part
		/// </summary>
		public string Type {
			get { return this._type; }
			set { this._type = value; }
		}

		/// <summary>
		/// Get or set the check number
		/// </summary>
		public int? CheckNumber {
			get { return this._checkNumber; }
			set {
				this._checkNumber = value;

				if(!this._emitent.IsNullOrTrimmedEmpty()
				&& !this._type.IsNullOrTrimmedEmpty()) {
					int checkNumber = ComputeChecksum(this._emitent + this._type);

					if(this._checkNumber != checkNumber) {
						this._checkNumber = checkNumber;
					}
				}
			}
		}

		/// <summary>
		/// Provides a value that indicates if the CUSIP is null
		/// </summary>
		public bool IsNull {
			get {
				if(this.Emitent.IsNullOrTrimmedEmpty()
				&& this.Type.IsNullOrTrimmedEmpty()) {
					return true;
				}

				return false;
			}
		}

		/// <summary>
		/// Returns a string that represents a CUSIP number
		/// </summary>
		/// <returns>Returns a string that represents a CUSIP number.</returns>
		public override string ToString() {
			StringBuilder str = new StringBuilder();

			str.Append(this._emitent);
			str.Append(this._type);

			if(this._checkNumber.HasValue) {
				str.Append(this._checkNumber);
			}

			return str.ToString();
		}

		/// <summary>
		/// Returns the hashcode of the object
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode() {
			return base.GetHashCode();
		}

		/// <summary>
		/// Converts implizit a string containing a CUSIP to a CUSIP object
		/// </summary>
		/// <param name="cusip">A string that contains a CUSIP.</param>
		/// <returns>Returns a CUSIP object on base of a string containing a CUSIP.</returns>
		public static implicit operator CUSIP(string cusip) {
			if(cusip == null) {
				return new CUSIP(null, null);
			}

			return CUSIP.Parse(cusip);
		}

		/// <summary>
		/// Converts implizit a CUSIP object to a string
		/// </summary>
		/// <param name="cusip">The CUSIP object to be converted.</param>
		/// <returns>Returns a string containing a CUSIP.</returns>
		public static implicit operator string(CUSIP cusip) {
			if(cusip == null
			|| (cusip.Emitent == null && cusip.Type == null)) {
				return null;
			}

			return cusip.ToString();
		}

		/// <summary>
		/// Checks if a object equals the current object
		/// </summary>
		/// <param name="obj">Another CUSIP object.</param>
		/// <returns>Returns a value indicating if the parameter object equals the current object.</returns>
		public override bool Equals(object obj) {
			try {
				if(obj == null) {
					return false;
				}

				CUSIP cusip = (CUSIP)obj;

				if(this.Emitent == cusip.Emitent
				&& this.Type == cusip.Type
				&& this.CheckNumber == cusip.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks if the first CUSIP object equals the second CUSIP object
		/// </summary>
		/// <param name="cusip1">The first CUSIP object.</param>
		/// <param name="cusip2">The second CUSIP object.</param>
		/// <returns>Returns a value indicating if the first CUSIP object equals the second CUSIP object.</returns>
		public static bool operator ==(CUSIP cusip1, CUSIP cusip2) {
			try {
				if(object.ReferenceEquals(cusip1, cusip2)) {
					return true;
				}

				if(((object)cusip1 != null) && ((object)cusip2 == null)) {
					return false;
				}

				if(((object)cusip1 == null) && ((object)cusip2 != null)) {
					return false;
				}

				if(cusip1.IsNull && !cusip2.IsNull) {
					return false;
				}

				if(!cusip1.IsNull && cusip2.IsNull) {
					return false;
				}

				if((cusip1.IsNull && cusip2.IsNull)
				|| (cusip1.IsNull && cusip2 == null)
				|| (cusip1 == null && cusip2.IsNull)) {
					return true;
				}

				if(cusip1.Emitent == cusip2.Emitent
				&& cusip1.Type == cusip2.Type
				&& cusip1.CheckNumber == cusip2.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks if the first CUSIP object does not equal the second CUSIP object
		/// </summary>
		/// <param name="cusip1">The first CUSIP object.</param>
		/// <param name="cusip2">The second CUSIP object.</param>
		/// <returns>Returns a value indicating if the first CUSIP object does not equal the second CUSIP object.</returns>
		public static bool operator !=(CUSIP cusip1, CUSIP cusip2) {
			try {
				if(object.ReferenceEquals(cusip1, cusip2)) {
					return false;
				}

				if(((object)cusip1 == null) || ((object)cusip2 == null)) {
					return true;
				}

				if(cusip1.IsNull && cusip2.IsNull) {
					return false;
				}

				if(cusip1.Emitent != cusip2.Emitent
				|| cusip1.Type != cusip2.Type
				|| cusip1.CheckNumber != cusip2.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks the internal parts of the CUSIP for special characters
		/// </summary>
		/// <returns>Returns a value indicating if the internal parts of the CUSIP contains special characters.</returns>
		public bool ContainsSpecialChars() {
			return ContainsSpecialChars(this._emitent, this._type);
		}

		#region Static Members

		/// <summary>
		/// Checks if a string is a CUSIP
		/// </summary>
		/// <param name="value">A string that could contain a CUSIP.</param>
		/// <returns>Returns a value indicating if the string containing a CUSIP.</returns>
		public static bool IsCUSIP(string value) {
			bool result = false;

			if(value != null
			&& (value.Length == 8 || value.Length == 9)) {
				try {
					if(value.Length == 9
					&& value.Substring(8, 1).ToInt32() == ComputeChecksum(value.Substring(0, 8))) {
						result = true;
					}
					else if(value.Length == 8) {
						CUSIP cusip;

						if(TryParse(value, out cusip)) {
							if(cusip.Emitent != null
							&& cusip.Emitent.Length == 6
							&& cusip.Type != null
							&& cusip.Type.Length == 2
							&& cusip.CheckNumber.HasValue
							&& !cusip.ContainsSpecialChars()) {
								result = true;
							}
						}
						else {
							result = false;
						}
					}
				}
				catch(Exception) {
					result = false;
				}
			}

			return result;
		}

		/// <summary>
		/// Parse a string into a CUSIP object and computes the check number if there is no check number
		/// </summary>
		/// <param name="value">A string containing a CUSIP</param>
		/// <param name="cusip">The value that could be set</param>
		/// <returns>Returns a value indicating if the string could be parsed to a CUSIP.</returns>
		public static bool TryParse(string value, out CUSIP cusip) {
			try {
				cusip = Parse(value);
				return true;
			}
			catch(Exception) {
				cusip = null;
				return false;
			}
		}

		/// <summary>
		/// Parse a string into a CUSIP object and computes the check number if there is no check number
		/// </summary>
		/// <param name="value">A string containing a CUSIP</param>
		/// <returns>Returns a CUSIP object.</returns>
		public static CUSIP Parse(string value) {
			if(value == null) {
				throw new ArgumentNullException("The value parameter cannot be null!");
			}

			if(value.Length < 8
			|| value.Length > 9) {
				throw new ArgumentException("The length of the value parameter must be 8 (without checknumber) or 9 (with checknumber)!");
			}

			string emitent = value.Substring(0, 6);
			string type = value.Substring(6, 2);
			int checkNumber = 0;

			if(ContainsSpecialChars(emitent, type)) {
				throw new ArgumentException("The value contains incorrect characters and/or special characters!");
			}

			if(value.Length == 9) {
				checkNumber = value.Substring(8, 1).ToInt32();

				if(checkNumber != ComputeChecksum(value.Substring(0, 8))) {
					checkNumber = ComputeChecksum(value.Substring(0, 8));
				}
			}
			else {
				checkNumber = ComputeChecksum(value.Substring(0, 8));
			}

			return new CUSIP(emitent, type, checkNumber);
		}

		/// <summary>
		/// Computes a checknumber on base of the double-add-double method
		/// </summary>
		/// <param name="value">A value for the double-add-double method</param>
		/// <returns></returns>
		public static int ComputeChecksum(string value) {
			bool eachLetterForCalculation = false;
			bool checksuminValueIncluded = (value.Length == 8 ? false : true);

			return DoubleAddDouble.Compute(value, eachLetterForCalculation, checksuminValueIncluded);
		}

		/// <summary>
		/// Checks the two base parts of the CUSIP for special characters
		/// </summary>
		/// <param name="emitent">The emitent part to check.</param>
		/// <param name="type">The type part to check.</param>
		/// <returns>Returns a value indicating if the two base parts of the CUSIP contains special characters.</returns>
		public static bool ContainsSpecialChars(string emitent, string type) {
			foreach(char c in emitent) {
				if(!c.ToString().IsNumeric()
				&& DoubleAddDouble.GetLetterNumber(c.ToString()) == -1) {
					return true;
				}
			}

			foreach(char c in type) {
				if(!c.ToString().IsNumeric()
				&& DoubleAddDouble.GetLetterNumber(c.ToString()) == -1) {
					return true;
				}
			}

			return false;
		}

		#endregion
	}
}
